package com.owc.service.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.owc.common.Const.ConfigConst;
import com.owc.common.Const.Const;
import com.owc.common.context.SessionHelper;
import com.owc.common.enums.ExceptionEnum;
import com.owc.common.enums.SendCodeEnum;
import com.owc.common.exception.BadRequestException;
import com.owc.common.redis.CacheBuildKey;
import com.owc.common.redis.RedisUtil;
import com.owc.common.sms.SmsUtils;
import com.owc.common.tool.*;
import com.owc.dao.NoticeMapper;
import com.owc.dao.SystemConfigMapper;
import com.owc.dao.TokenMapper;
import com.owc.dao.VersionMapper;
import com.owc.domain.PageBean;
import com.owc.domain.UserExample;
import com.owc.domain.dto.*;
import com.owc.domain.entity.Notice;
import com.owc.domain.entity.Token;
import com.owc.domain.entity.User;
import com.owc.domain.entity.Version;
import com.owc.domain.vo.app.AuthCodeVo;
import com.owc.filter.content.SecurityContextHolder;
import com.owc.service.OwcService;
import com.owc.service.app.UserService;
import com.owc.service.app.WalletService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author Jackies
 * @Date 2019/12/2 22:12
 * @Description TODO://
 **/
@Service
@Slf4j
public class OwcServiceImpl implements OwcService {

    @Resource
    private TokenMapper tokenMapper;
    @Resource
    private NoticeMapper noticeMapper;

    @Autowired
    private UserService userService;
    @Autowired
    private SmsUtils smsUtils;
    @Autowired
    private SystemConfigMapper systemConfigMapper;
    @Autowired
    private VersionMapper versionMapper;
    @Autowired
    private WalletService walletService;

    @Override
    public boolean sendCode(AuthCodeVo authCodeVo) {
        //Check if the condition is met according to type
        SendCodeEnum sendCodeEnum = sendCodeFrontCheck(authCodeVo);

        Integer code = UtilsService.genRandom();
        try {
            //Cache code.
            RedisUtil._REDIS_MODULE.set(sendCodeEnum.getRule().execute(authCodeVo.getPhone()),
                    code, CacheBuildKey._TIME_MINUTE_ONE * 5L);

            smsUtils.send(authCodeVo.getPhone(),code + "");
        } catch (Exception e) {
            log.error("Send verification code throw error [{}]",e.getMessage());
            e.printStackTrace();
            throw new BadRequestException("Send verification code failed. errMsg:[{" + e.getMessage()+ "}]");
        }

        log.info("Send verification code. phone:[{}] type:[{}] code:[{}]",
                authCodeVo.getPhone(),SendCodeEnum.match(authCodeVo.getType()).get().getDesc(),code);

        return true;
    }

    /**
     * Pre-check by type
     */
    private SendCodeEnum sendCodeFrontCheck(AuthCodeVo authCodeVo) {

        if(!authCodeVo.getType().equals(SendCodeEnum.BACKPASSWORD.getType())){
            //If it is not the password type, you need to verify the parameters
            Assert.notNull(authCodeVo.getPhone(),ExceptionEnum.PLEASE_ENTER_PHONE.getMessage());
            Assert.isTrue(ValidationUtils.isMobile(authCodeVo.getPhone()),ExceptionEnum.INVALID_PHONE_ERR.getMessage());
        }else{
            //If it is to modify the password type, resolve the phone according to the token
            Optional<User> optionalUser = userService.selectByUserName(SecurityContextHolder.getCurrentUserName());
            if(!optionalUser.isPresent()) {
                throw new BadRequestException(ExceptionEnum.USER_INVALID_ERR);
            }
            //Set current user phone.
            authCodeVo.setPhone(optionalUser.get().getPhone());
        }

        //Default err message.
        String defaultErrMessage = ExceptionEnum.INVALID_PARAMS_ERR.getMessage();
        //Find enum example by authCodeVo.type
        Optional<SendCodeEnum> opt = SendCodeEnum.match(authCodeVo.getType());
        Assert.isTrue(opt.isPresent(), defaultErrMessage);

        SendCodeEnum sendCodeEnum1 = opt.get();

        switch (sendCodeEnum1){
            case LOGIN:
            case BACKPASSWORD:
                Assert.isTrue(alreadyRegister(authCodeVo.getPhone()),"Mobile number is not registered");
                break;
            case REGISTER:
                Assert.isTrue(!alreadyRegister(authCodeVo.getPhone()),"Mobile number has been registered");
                break;
            default:
                throw new BadRequestException(defaultErrMessage);
        }
        return opt.get();
    }

    /**
     * valid phone is already register
     */
    private boolean alreadyRegister(String phone){
        return userService.phoneIsRegister(phone);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T getSystemConfig(String key,Class<T> cls) {

        String configVal = systemConfigMapper.selectByKey(key);
        if(configVal == null){
            return null;
        }
        return (T)ReflectionUtils.parserAttrType(cls,configVal);
    }

    @Override
    public String getTokenName(Long tokenId) {
        return Optional.ofNullable(tokenMapper.selectByPrimaryKey(tokenId))
                .map(Token::getTokenName)
                .orElse("");
    }

    @Override
    public H5 getH5Url() {
        return new H5(getSystemConfig(Const.h5Url,String.class));
    }

    @Override
    public VersionDTO getVersionInfo() {
        Integer client = SessionHelper.getRequestClient();

        Version version = versionMapper.selectNewnestVersionByClient(client);
        if(version == null){
            throw new BadRequestException("Did not find any version.");
        }

        if(Objects.equals(SessionHelper.getRequestVersion(), version.getVersion())){
            return new VersionDTO();
        }

        return new VersionDTO().convertFrom(versionMapper.selectNewnestVersionByClient(client));
    }

    @Override
    public TransferNotice getTransferNotice() {
        return new TransferNotice(getSystemConfig(Const.WITHDRAW_MATTER,String.class));
    }

    @Override
    public <T> Optional<UserExample> converterFromExample(@Nullable T t) {
        return Optional.ofNullable(BeanUtils.transformFrom(t,UserExample.class));
    }

    @Override
    public CommonInfo getCommonInfo(Long tokenId) {
        return new CommonInfo(getTransferNotice(),walletService.getTokenBalance(tokenId),walletService.getTokenHandlingFee(tokenId));
    }

    @Override
    public List<Token> getAllToken() {
        return tokenMapper.selectAll();
    }

    @Override
    public PageBean<NoticeDTO> getNotices(Integer pageIndex,Integer pageSize) {
        PageHelper.startPage(pageIndex,pageSize);

        List<Notice> list = noticeMapper.selectAll();
        PageInfo<Notice> pageInfo = new PageInfo<>(list);

        List<NoticeDTO> noticeDtos = BeanUtils.coventObject(list,NoticeDTO.class);

        //获取已经阅读过的公告ID
        Set<Long> readIds = getLastReadNoticeIds(SecurityContextHolder.getCurrentUserId());
        if(readIds != null){
            noticeDtos.parallelStream().forEach(item -> {
                if(readIds.contains(item.getId())){
                    item.setWatch(true);
                }else{
                    item.setWatch(false);
                }
            });
        }
        //缓存当前用户阅读的公告id
        cacheReadNoticeIds(SecurityContextHolder.getCurrentUserId(),list
                .parallelStream()
                .map(Notice::getId)
                .collect(Collectors.toList()));
        return PageBean.pageBean(pageInfo.getTotal(),noticeDtos);
    }

    @Override
    public Set<Long> getLastReadNoticeIds(Long userId) {
        String cacheKey = CacheBuildKey.buildNoticeReadKey(SecurityContextHolder.getCurrentUserId());
        return RedisUtil.getInstance().smembers(cacheKey,Long.class,null);
    }

    @Override
    public void cacheReadNoticeIds(Long userId, List ids) {
        RedisUtil.getInstance().sadd(CacheBuildKey.buildNoticeReadKey(SecurityContextHolder.getCurrentUserId()),ids,null);
    }

    @Override
    public CheckNoticeDTO checkNotice() {
        CheckNoticeDTO dto = new CheckNoticeDTO();
        //获取所有通知
        List<Notice> allNotice = noticeMapper.selectAll();
        if(allNotice == null || allNotice.isEmpty()){
            return dto;
        }
        //获取当前用户已经阅读的通知ID
        Set<Long> ids = getLastReadNoticeIds(SecurityContextHolder.getCurrentUserId());

        Boolean exist = allNotice.size() > ids.size();
        dto.setExist(exist);
        if(exist){
            dto.setCnt(allNotice.size() - ids.size());
        }
        dto.setTime(DateUtils.formatYMDHMS(new Date()));
        return dto;
    }

    @Override
    public <T> T injectDefaultAttr(T source) {
        ReflectionUtils.setFieldValue(source,"createTime",DateUtils.formatYMDHMS(new Date()));
        ReflectionUtils.setFieldValue(source,"createName",SecurityContextHolder.getCurrentUserName());
        ReflectionUtils.setFieldValue(source,"createId",SecurityContextHolder.getCurrentUserId());
        return source;
    }

    @Override
    public List<CommonInfoDTO> getOwcBasisInfo() {

        List<CommonInfoDTO> commonInfoDTOs = new ArrayList<>();

        for (String key : ConfigConst.allKeys) {
            CommonInfoDTO c = new CommonInfoDTO();
            c.setKey(key);
            c.setValue(getSystemConfig(key,String.class));
            commonInfoDTOs.add(c);
        }
        return commonInfoDTOs;
    }
}
